﻿using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;

namespace Useful.Reflection
{
    public class ReflectionHelperException : ApplicationException
    {
        public ReflectionHelperException(string message, Exception innerException)
            : base(message, innerException)
        {
        }
    }

    public static class ReflectionHelper
    {
        public static T GetPropertyValue<T>(string name, object obj)
        {
            Type type = obj.GetType();

            try
            {
                PropertyInfo pi = type.GetProperty(name, BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance);
                object oValue = pi.GetValue(obj, null);

                return (T)oValue;
            }
            catch (Exception ex)
            {
                throw new ReflectionHelperException(String.Format("Не удаётся получить значение для свойства '{0}'", name), ex);
            }
        }

        public static object GetProperty(string name, object obj)
        {
            Type type = obj.GetType();

            try
            {
                object oValue = type.InvokeMember(name, BindingFlags.GetProperty, null, obj, null);

                return oValue;
            }
            catch (Exception ex)
            {
                throw new ReflectionHelperException(String.Format("Не удаётся получить значение для свойства '{0}'", name), ex);
            }
        }

        public static T GetPropertyByIndex<T>(string name, object obj, object[] indexes)
        {
            Type type = obj.GetType();

            try
            {
                PropertyInfo pi = type.GetProperty(name, BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance);
                object oValue = pi.GetValue(obj, indexes);
                return (T)oValue;
            }
            catch (Exception ex)
            {
                throw new ReflectionHelperException(String.Format("Не удаётся получить значение для свойства '{0}' по индексам: '{1}'", name, ObjectArrayToString(indexes)), ex);
            }
        }

        private static string ObjectToString(object value)
        {
            return value != null ? value.ToString() : "<null>";
        }

        private static string ObjectArrayToString(object[] values)
        {
            StringBuilder sb = new StringBuilder();

            if (values != null)
            {
                for (int i = 0; i < values.Length; i++)
                {
                    if (i != 0)
                        sb.Append(", ");

                    sb.Append(ObjectToString(values[i]));
                }
            }
            else
                sb.Append("<null>");

            return sb.ToString();
        }

        public static bool HasCOMException(Exception ex)
        {
            bool res = false;

            if (ex is COMException)
                res = true;
            else if (ex.InnerException != null)
                return HasCOMException(ex.InnerException);

            return res;
        }

        public static object GetPropertyByIndex(string name, object obj, object[] indexes)
        {
            Type type = obj.GetType();

            try
            {
                object oValue = type.InvokeMember(name, BindingFlags.GetProperty, null, obj, indexes);

                return oValue;
            }
            catch (Exception ex)
            {
                throw new ReflectionHelperException(String.Format("Не удаётся получить значение для свойства '{0}' по индексам: '{1}'", name, ObjectArrayToString(indexes)), ex);
            }
        }

        public static void SetPropertyValue(object obj, string name, object value)
        {
            Type type = obj.GetType();

            try
            {
                type.InvokeMember(name, BindingFlags.SetProperty, null, obj, new object[] { value });
            }
            catch (Exception ex)
            {
                throw new ReflectionHelperException(String.Format("Не удаётся установить значение '{0}' для свойства '{1}'", ObjectToString(value), name), ex);
            }
        }

        public static object InvokeMethod(object obj, string name)
        {
            Type type = obj.GetType();

            try
            {
                return type.InvokeMember(name, BindingFlags.InvokeMethod, null, obj, null);
            }
            catch (Exception ex)
            {
                throw new ReflectionHelperException(String.Format("Не удаётся вызвать метод '{0}'", name), ex);
            }
        }

        public static object InvokeMethod(object obj, string name, object[] args)
        {
            Type type = obj.GetType();

            try
            {
                return type.InvokeMember(name, BindingFlags.InvokeMethod, null, obj, args);
            }
            catch (Exception ex)
            {
                throw new ReflectionHelperException(String.Format("Не удаётся вызвать метод '{0}' с параметрами: '{1}'", name, ObjectArrayToString(args)), ex);
            }
        }

        public static string GetExceptionMessage(Exception exx)
        {
            StringBuilder sb = new StringBuilder(exx.Message);

            if (exx.InnerException != null)
                sb.AppendLine().AppendLine(GetExceptionMessage(exx.InnerException));

            return sb.ToString();
        }
    }
}
